#include "../pch.h"
#include "REFXMLElementList.h"
#include "core/REFNameTable.h"

namespace BFX
{

REFXMLElementList::REFXMLElementList(REFXMLNode* pParent, const UniString& strName)
{
	BFX_ASSERT(pParent != NULL);
	BFX_ASSERT(pParent->GetNodeType() == XMLNodeType_Element || pParent->GetNodeType() == XMLNodeType_Document);
	BFX_ASSERT(pParent->GetDocument() != NULL);

	m_pRootNode = pParent;
	REFNameTable* pnt = pParent->GetDocument()->GetNameTable();
	m_nChangeCount = pParent->GetDocument()->GetChangeCount();
	m_strAsterisk = pnt->Add(USTR("*"));
	m_strName = pnt->Add(strName);
	m_nCurIndex = -1;
	m_pCurElement = pParent;
	m_nMatchCount = -1;
}

REFXMLElementList::REFXMLElementList(REFXMLNode* pParent, const UniString& strLocalName, const UniString& strNamespaceURI)
{
	BFX_ASSERT(pParent != NULL);
	BFX_ASSERT(pParent->GetNodeType() == XMLNodeType_Element || pParent->GetNodeType() == XMLNodeType_Document);
	BFX_ASSERT(pParent->GetDocument() != NULL);

	m_pRootNode = pParent;
	REFNameTable* pnt = pParent->GetDocument()->GetNameTable();
	m_nChangeCount = pParent->GetDocument()->GetChangeCount();
	m_strAsterisk = pnt->Add(USTR("*"));
	m_strLocalName = pnt->Add(strLocalName);
	m_strNamespaceURI = pnt->Add(strNamespaceURI);
	m_nCurIndex = -1;
	m_pCurElement = pParent;
	m_nMatchCount = -1;
}

REFXMLElementList::~REFXMLElementList()
{
}

// Retrieves a node at the given index.
REFXMLNode* REFXMLElementList::operator[](int iIndex) const
{
	return GetItem(iIndex);
}

// Retrieves a node at the given index.
REFXMLNode* REFXMLElementList::GetItem(int nIndex) const
{
	if (m_pRootNode == NULL || nIndex < 0)
		return NULL;

	ConcurrencyCheck();

	if (m_nCurIndex == nIndex)
		return m_pCurElement;
	int nDiff = nIndex - m_nCurIndex;
	bool bForward = ( nDiff > 0 );
	if ( nDiff < 0 )
		nDiff = -nDiff;

	REFXMLNode* pNode;
	if ((pNode = GetNthMatchingNode(m_pCurElement, bForward, nDiff)) != NULL)
	{
		m_nCurIndex = nIndex;
		m_pCurElement = pNode;
		return m_pCurElement;
	}
	return NULL;
}

// Gets the number of nodes in this list.
int REFXMLElementList::GetCount() const
{
	ConcurrencyCheck();

	if (m_nMatchCount < 0)
	{
		int nCurrMatchCount = 0;
		REFXMLNode* pNode = m_pRootNode;
		while ((pNode = GetMatchingNode(pNode, true)) != NULL)
		{
			nCurrMatchCount++;
		}
		m_nMatchCount = nCurrMatchCount;
	}
	return m_nMatchCount;
}

REFXMLNode* REFXMLElementList::GetNthMatchingNode(REFXMLNode* pCurNode, bool bNext, int nCount) const
{
	BFX_ASSERT(pCurNode!= NULL);
	REFXMLNode* pRetNode = pCurNode;
	for (int i = 0 ; i < nCount; i++)
	{
		pRetNode = GetMatchingNode(pRetNode, bNext);
		if (pRetNode == NULL)
			return NULL;
	} 
	return pRetNode;
}

REFXMLNode* REFXMLElementList::GetMatchingNode(REFXMLNode* pCurNode, bool bNext) const
{
	BFX_ASSERT(pCurNode!= NULL);
	REFXMLNode* pRetNode = pCurNode;
	do
	{
		if (bNext)
			pRetNode = NextElemInPreOrder(pRetNode);
		else
			pRetNode = PrevElemInPreOrder(pRetNode);
	} while (pRetNode != NULL && !IsMatch(pRetNode));
	return pRetNode;
}

// return the next element node that is in PreOrder
REFXMLNode* REFXMLElementList::NextElemInPreOrder(REFXMLNode* pCurNode) const
{
	BFX_ASSERT(pCurNode != NULL);
	//For preorder walking, first try its child
	REFXMLNode* pRetNode = pCurNode->GetFirstChild();
	if (pRetNode == NULL)
	{
		//if no child, the next node forward will the be the NextSibling of the first ancestor which has NextSibling
		//so, first while-loop find out such an ancestor (until no more ancestor or the ancestor is the rootNode
		pRetNode = pCurNode;
		while (pRetNode != NULL && pRetNode != m_pRootNode && pRetNode->GetNextSibling() == NULL)
		{
			pRetNode = pRetNode->GetParentNode();
		}
		//then if such ancestor exists, set the retNode to its NextSibling
		if (pRetNode != NULL && pRetNode != m_pRootNode)
			pRetNode = pRetNode->GetNextSibling();
	}
	if (pRetNode == m_pRootNode) 
		pRetNode = NULL;
	return pRetNode;
}

// return the previous element node that is in PreOrder
REFXMLNode* REFXMLElementList::PrevElemInPreOrder(REFXMLNode* pCurNode) const
{
	BFX_ASSERT(pCurNode != NULL);
	//For preorder walking, the previous node will be the right-most node in the tree of PreviousSibling of the curNode
	REFXMLNode* pRetNode = pCurNode->GetPreviousSibling();
	// so if the PreviousSibling is not null, going through the tree down to find the right-most node
	while (pRetNode != NULL)
	{
		if (pRetNode->GetLastChild() == NULL)
			break;
		pRetNode = pRetNode->GetLastChild();
	}
	// if no PreviousSibling, the previous node will be the curNode's parentNode
	if (pRetNode == NULL)
		pRetNode = pCurNode->GetParentNode();
	if (pRetNode == m_pRootNode)
		pRetNode = NULL;
	return pRetNode;
}

// if the current node a matching element node
bool REFXMLElementList::IsMatch(REFXMLNode* pCurNode) const
{
	if (pCurNode->GetNodeType() == XMLNodeType_Element)
	{
		if (!m_strName.IsEmpty())
		{
			if (m_strName == m_strAsterisk || pCurNode->GetName() == m_strName)
				return true;
		} 
		else
		{
			if ((m_strLocalName == m_strAsterisk || pCurNode->GetLocalName() == m_strLocalName) && 
				(m_strNamespaceURI == m_strAsterisk || pCurNode->GetNamespaceURI() == m_strNamespaceURI))
			{
				return true;
			}
		}
	}     
	return false;
}

void REFXMLElementList::ConcurrencyCheck() const
{
	// compares local / document update version.
	int nChangeCount = m_pRootNode->GetDocument()->GetChangeCount();
	if (nChangeCount != m_nChangeCount)
	{
		// we need to reconstruct the list when the document changed. (eg, inserts, removes...)
		m_nCurIndex = -1;
		m_pCurElement = m_pRootNode;

		m_nMatchCount = -1;
		m_nChangeCount = nChangeCount;
	}
}

}	//	namespace BFX
